﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace bianyi
{
    public class Parser
    {
        public Parser(string filename)
        {
            keywords = new List<Tuple<string, Token>>();
            keywords.Add(Tuple.Create("if", Token.KW_IF));
            keywords.Add(Tuple.Create("else", Token.KW_ELSE));
            keywords.Add(Tuple.Create("while", Token.KW_WHILE));
            keywords.Add(Tuple.Create("null", Token.KW_NULL));
            keywords.Add(Tuple.Create("true", Token.KW_TRUE));
            keywords.Add(Tuple.Create("false", Token.KW_FALSE));
            keywords.Add(Tuple.Create("for", Token.KW_FOR));

            keywords.Add(Tuple.Create("func", Token.KW_FUNC));
            keywords.Add(Tuple.Create("return", Token.KW_RETURN));
            keywords.Add(Tuple.Create("break", Token.KW_BREAK));
            keywords.Add(Tuple.Create("continue", Token.KW_CONTINUE));

            if (!File.Exists(filename))
            {
                Console.WriteLine("ParserError: can not open source file");
                Console.Read();
            }
            fs = new StreamReader(filename);
        }

        private List<Tuple<string, Token>> keywords;

        private Tuple<Token, string> currentToken;

        //FileStream fs;

        private StreamReader fs;
        //StreamReader sr = new StreamReader(fs);

        private int line = 1;

        private int column = 0;

        private string getCurrentLexeme()
        {
            return currentToken.Item2;
        }

        private Token getCurrentToken()
        {
            return currentToken.Item1;
        }

        public void parse(Runtime rt)
        {
            currentToken = next();
            if (getCurrentToken() == Token.TK_EOF)
            {
                return;
            }
            do
            {
                if (getCurrentToken() == Token.KW_FUNC)
                {
                    var f = parseFuncDef(rt);
                    rt.addFunction(f.name, f);
                }
                else
                {
                    rt.addStatementt(parseStatement());
                }
            } while (getCurrentToken() != Token.TK_EOF);
        }

        private Function parseFuncDef(Runtime rt)
        {
            //assert(getCurrentToken() == KW_FUNC);
            //currentToken = next();

            //// Check if function was already be defined
            //if (context->hasFunction(getCurrentLexeme()))
            //{
            //    panic("SyntaxError: multiply function definitions of %s found",
            //          getCurrentLexeme().c_str());
            //}

            //auto* node = new nyx::Function;
            //node->name = getCurrentLexeme();
            //currentToken = next();
            //assert(getCurrentToken() == TK_LPAREN);
            //node->params = parseParameterList();
            //node->block = parseBlock();
            var node = new Function();
            return node;
        }

        public Statement parseStatement()
        {
            Statement node;
            switch (getCurrentToken())
            {
                case Token.KW_IF:
                    currentToken = next();
                    node = parseIfStmt();
                    break;

                case Token.KW_WHILE:
                    currentToken = next();
                    node = parseWhileStmt();
                    break;

                case Token.KW_RETURN:
                    currentToken = next();
                    node = parseReturnStmt();
                    break;

                case Token.KW_BREAK:
                    currentToken = next();
                    node = new BreakStmt(line, column);
                    break;

                case Token.KW_CONTINUE:
                    currentToken = next();
                    node = new ContinueStmt(line, column);
                    break;

                default:
                    node = parseExpressionStmt();
                    break;
            }
            return node;
        }

        private bool anyone(params object[] aa)
        {
            for (int i = 1; i < aa.Count(); i++)
            {
                if (aa[0].ToString() == aa[i].ToString()) { return true; }
            }

            return false;
        }

        private Statement parseExpressionStmt()
        {
            ExpressionStmt node = null;
            var p = parseExpression();
            if (p != null)
            {
                node = new ExpressionStmt(p, line, column);
            }
            return node;
        }

        public static void printLex(string fileName)
        {
            Parser p = new Parser(fileName);
            Tuple<Token, string> tk;
            do
            {
                tk = p.next();
                //std::cout << "[" << std::get < 0 > (tk) << "," << std::get < 1 > (tk) << "]\n";
                Console.Write("[" + tk.Item1 + "," + tk.Item2 + "]\n");
            } while (tk.Item1 != Token.TK_EOF);
        }

        //Expression parseExpression(short oldPrecedence)
        //{
        //    auto* p = parseUnaryExpr();

        //    if (anyone(getCurrentToken(), TK_ASSIGN, TK_PLUS_AGN, TK_MINUS_AGN,
        //               TK_TIMES_AGN, TK_DIV_AGN, TK_MOD_AGN))
        //    {
        //        if (typeid(*p) != typeid(IdentExpr) &&
        //            typeid(*p) != typeid(IndexExpr))
        //        {
        //            panic("SyntaxError: can not assign to %s", typeid(*p).name());
        //        }
        //        auto* assignExpr = new AssignExpr(line, column);
        //        assignExpr->opt = getCurrentToken();
        //        assignExpr->lhs = p;
        //        currentToken = next();
        //        assignExpr->rhs = parseExpression();
        //        return assignExpr;
        //    }

        //    while (anyone(getCurrentToken(), TK_BITOR, TK_BITAND, TK_BITNOT, TK_LOGOR,
        //                  TK_LOGAND, TK_LOGNOT, TK_EQ, TK_NE, TK_GT, TK_GE, TK_LT,
        //                  TK_LE, TK_PLUS, TK_MINUS, TK_MOD, TK_TIMES, TK_DIV))
        //    {
        //        short currentPrecedence = Parser::precedence(getCurrentToken());
        //        if (oldPrecedence > currentPrecedence)
        //        {
        //            return p;
        //        }
        //        auto tmp = new BinaryExpr(line, column);
        //        tmp->lhs = p;
        //        tmp->opt = getCurrentToken();
        //        currentToken = next();
        //        tmp->rhs = parseExpression(currentPrecedence + 1);
        //        p = tmp;
        //    }
        //    return p;
        //}

        private Statement parseIfStmt()
        {
            //throw new NotImplementedException();
            var node = new IfStmt(line, column);
            // 吃掉左括号
            currentToken = next();
            // 解析条件表达式
            node.cond = parseExpression();
            // 吃掉右括号
            if (getCurrentToken() == Token.TK_RPAREN)
                currentToken = next();

            // 解析语句块
            node.block = parseBlock();

            // 如果当前关键字是else，那么还有else语句块
            if (getCurrentToken() == Token.KW_ELSE)
            {
                // 吃掉else关键字
                currentToken = next();
                // 解析else语句块
                node.elseBlock = parseBlock();
            }

            return node;
        }

        public ReturnStmt parseReturnStmt()
        {
            var node = new ReturnStmt(line, column);
            // 解析返回值表达式
            node.ret = parseExpression();
            return node;
        }

        private Statement parseWhileStmt()
        {
            var node = new WhileStmt(line, column);
            currentToken = next();
            node.cond = (Expression)parseExpression();
            if (getCurrentToken() == Token.TK_RPAREN)
                currentToken = next();
            node.block = (Block)parseBlock();
            return node;
        }

        public List<Statement> parseStatementList()
        {
            List<Statement> node = new List<Statement>();
            Statement p;
            while ((p = parseStatement()) != null)
            {
                node.Add(p);
            }
            return node;
        }

        private Block parseBlock()
        {
            Block node = new Block() { };
            currentToken = next();
            node.stmts = parseStatementList();
            if (getCurrentToken() == Token.TK_RBRACE)
                currentToken = next();
            return node;
        }

        private Expression parseExpression(short oldPrecedence = 1)
        {
            var p = parseUnaryExpr();

            if (anyone(
                getCurrentToken(),
                Token.TK_ASSIGN,
                Token.TK_PLUS_AGN,
                Token.TK_MINUS_AGN,
                Token.TK_TIMES_AGN,
                Token.TK_DIV_AGN,
                Token.TK_MOD_AGN))
            {
                if (!(p is IdentExpr) &&
                    !(p is IndexExpr))
                {
                    Console.WriteLine("SyntaxError: can not assign to %s", p.GetType().Name);
                }
                var assignExpr = new AssignExpr(line, column);
                assignExpr.opt = getCurrentToken();
                assignExpr.lhs = p;
                currentToken = next();
                assignExpr.rhs = parseExpression();
                return assignExpr;
            }

            while (anyone(
                getCurrentToken(),
                Token.TK_BITOR,
                Token.TK_BITAND,
                Token.TK_BITNOT,
                Token.TK_LOGOR,
                Token.TK_LOGAND,
                Token.TK_LOGNOT,
                Token.TK_EQ,
                Token.TK_NE,
                Token.TK_GT,
                Token.TK_GE,
                Token.TK_LT,
                Token.TK_LE,
                Token.TK_PLUS,
                Token.TK_MINUS,
                Token.TK_MOD,
                Token.TK_TIMES,
                Token.TK_DIV))
            {
                short currentPrecedence = this.precedence(getCurrentToken());
                if (oldPrecedence > currentPrecedence)
                {
                    return p;
                }
                var tmp = new BinaryExpr(line, column);
                tmp.lhs = p;
                tmp.opt = getCurrentToken();
                currentToken = next();
                tmp.rhs = parseExpression(Convert.ToInt16(currentPrecedence + 1));
                p = tmp;
            }
            return p;
        }

        private Expression parseUnaryExpr()
        {
            if (anyone(
                getCurrentToken(),
                Token.TK_MINUS,
                Token.TK_LOGNOT,
                Token.TK_BITNOT))
            {
                var val = new BinaryExpr(line, column);
                val.opt = getCurrentToken();
                currentToken = next();
                val.lhs = parseUnaryExpr();
                return val;
            }
            else if (anyone(
                getCurrentToken(),
                Token.LIT_DOUBLE,
                Token.LIT_INT,
                Token.LIT_STR,
                Token.LIT_CHAR,
                Token.TK_IDENT,
                Token.TK_LPAREN,
                Token.TK_LBRACKET,
                Token.KW_TRUE,
                Token.KW_FALSE,
                Token.KW_NULL))
            {
                return parsePrimaryExpr();
            }
            return null;
        }

        private Expression parsePrimaryExpr()
        {
            if (getCurrentToken() == Token.TK_IDENT)
            {
                var ident = getCurrentLexeme();
                currentToken = next();
                switch (getCurrentToken())
                {
                    case Token.TK_LPAREN:
                        {
                            currentToken = next();
                            var val = new FunCallExpr(line, column);
                            val.funcName = ident;
                            while (getCurrentToken() != Token.TK_RPAREN)
                            {
                                val.args.Add(parseExpression());
                                if (getCurrentToken() == Token.TK_COMMA)
                                {
                                    currentToken = next();
                                }
                            }
                            if (getCurrentToken() == Token.TK_RPAREN)
                                currentToken = next();
                            return val;
                        }
                    case Token.TK_LBRACKET:
                        {
                            return null;
                        }
                    default:
                        {
                            return new IdentExpr(ident, line, column);
                        }
                }
            }
            else if (getCurrentToken() == Token.LIT_STR)
            {
                var val = getCurrentLexeme();
                currentToken = next();
                var ret = new StringExpr(line, column);
                ret.literal = val;
                return ret;
            }
            else if (getCurrentToken() == Token.LIT_INT)
            {
                var val = getCurrentLexeme();
                currentToken = next();
                var ret = new IntExpr(line, column);
                ret.literal = Convert.ToInt32(val);
                return ret;
            }
            else if (getCurrentToken() == Token.LIT_DOUBLE)
            {
                var val = getCurrentLexeme();
                currentToken = next();
                var ret = new DoubleExpr(line, column);
                ret.literal = Convert.ToDouble(val);
                return ret;
            }
            else if (getCurrentToken() == Token.LIT_CHAR)
            {
                var val = getCurrentLexeme();
                currentToken = next();
                var ret = new CharExpr(line, column);
                ret.literal = Convert.ToChar(val);
                return ret;
            }
            else if (getCurrentToken() == Token.KW_TRUE || getCurrentToken() == Token.KW_FALSE)
            {
                var val = (Token.KW_TRUE == getCurrentToken());
                currentToken = next();
                var ret = new BoolExpr(line, column);
                ret.literal = val;
                return ret;
            }
            return null;
        }

        public Tuple<Token, string> next()
        {
            if (fs.EndOfStream)
            {
                return Tuple.Create(Token.TK_EOF, "");
            }

            char c = getNextChar();

            if (anyone(c, ' ', '\n', '\r', '\t'))
            {
                while (c == ' ' || c == '\n' || c == '\r' || c == '\t')
                {
                    if (c == '\n')
                    {
                        line++;
                        column = 0;
                    }
                    c = getNextChar();
                }
                if (fs.EndOfStream)
                {
                    return Tuple.Create(Token.TK_EOF, "");
                }
            }

            if (c == '#')
            {
            another_comment:
                while (c != '\n' && !fs.EndOfStream)
                {
                    c = getNextChar();
                }
                // consume newlines
                while (c == '\n')
                {
                    if (peekNextChar() == '\r')
                        c = getNextChar();
                    line++;
                    column = 0;
                    c = getNextChar();
                }
                if (c == '#')
                {
                    goto another_comment;
                }
                if (fs.EndOfStream)
                {
                    return Tuple.Create(Token.TK_EOF, "");
                }
            }
            if (c == '/' && peekNextChar() == '/')
            {
            another_comment1:
                if (peekNextChar() == '/')
                {

                    while (c != '\n' && !fs.EndOfStream)
                    {
                        c = getNextChar();
                    }
                    // consume newlines
                    while (c == '\n')
                    {
                        if (peekNextChar() == '\r')
                            c = getNextChar();
                        line++;
                        column = 0;
                        c = getNextChar();
                    }
                    if (c == '/')
                    {
                        goto another_comment1;
                    }
                    if (fs.EndOfStream)
                    {
                        return Tuple.Create(Token.TK_EOF, "");
                    }
                }

            }
            if (c >= '0' && c <= '9')
            {
                string lexeme = c.ToString();
                bool isDouble = false;
                char cn = peekNextChar();
                while ((cn >= '0' && cn <= '9') || (!isDouble && cn == '.'))
                {
                    if (c == '.')
                    {
                        isDouble = true;
                    }
                    c = getNextChar();
                    cn = peekNextChar();
                    lexeme += c;
                }
                return !isDouble ? make_tuple(Token.LIT_INT, lexeme)
                                 : make_tuple(Token.LIT_DOUBLE, lexeme);
            }

            if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')
            {
                string lexeme = c.ToString();
                char cn = peekNextChar();
                while ((cn >= 'a' && cn <= 'z') || (cn >= 'A' && cn <= 'Z') ||
                       (cn >= '0' && cn <= '9') || cn == '_')
                {
                    c = getNextChar();
                    lexeme += c;
                    cn = peekNextChar();
                }
                var result = keywords.Find(m => m.Item1 == lexeme);
                return result != null
                           ? make_tuple(result.Item2, lexeme)
                           : make_tuple(Token.TK_IDENT, lexeme);
            }

            if (c == '\'')
            {
                string lexeme = "";
                lexeme += getNextChar().ToString();
                if (peekNextChar() != '\'')
                {
                    Console.Write(
                        @"SynxaxError: a character literal should surround with single-quote");
                }
                c = getNextChar();
                return Tuple.Create(Token.LIT_CHAR, lexeme);
            }

            if (c == '"')
            {
                string lexeme = "";
                char cn = peekNextChar();
                while (cn != '"')
                {
                    c = getNextChar();
                    lexeme += c;
                    cn = peekNextChar();
                }
                c = getNextChar();
                return Tuple.Create(Token.LIT_STR, lexeme);
            }

            if (c == '[')
            {
                return Tuple.Create(Token.TK_LBRACKET, "[");
            }
            if (c == ']')
            {
                return Tuple.Create(Token.TK_RBRACKET, "]");
            }
            if (c == '{')
            {
                return Tuple.Create(Token.TK_LBRACE, "{");
            }
            if (c == '}')
            {
                return Tuple.Create(Token.TK_RBRACE, "}");
            }
            if (c == '(')
            {
                return Tuple.Create(Token.TK_LPAREN, "(");
            }
            if (c == ')')
            {
                return Tuple.Create(Token.TK_RPAREN, ")");
            }
            if (c == ',')
            {
                return Tuple.Create(Token.TK_COMMA, ",");
            }

            if (c == '+')
            {
                if (peekNextChar() == '=')
                {
                    c = getNextChar();
                    return Tuple.Create(Token.TK_PLUS_AGN, "+=");
                }
                return Tuple.Create(Token.TK_PLUS, "+");
            }
            if (c == '-')
            {
                if (peekNextChar() == '=')
                {
                    c = getNextChar();
                    return Tuple.Create(Token.TK_MINUS_AGN, "-=");
                }
                return Tuple.Create(Token.TK_MINUS, "-");
            }
            if (c == '*')
            {
                if (peekNextChar() == '=')
                {
                    c = getNextChar();
                    return Tuple.Create(Token.TK_TIMES_AGN, "*=");
                }
                return Tuple.Create(Token.TK_TIMES, "*");
            }
            if (c == '/' && peekNextChar() != '/')
            {
                if (peekNextChar() == '=')
                {
                    c = getNextChar();
                    return Tuple.Create(Token.TK_DIV_AGN, "/=");
                }


            }
            if (c == '%')
            {
                if (peekNextChar() == '=')
                {
                    c = getNextChar();
                    return Tuple.Create(Token.TK_MOD_AGN, "%=");
                }
                return Tuple.Create(Token.TK_MOD, "%");
            }
            if (c == '~')
            {
                return Tuple.Create(Token.TK_BITNOT, "~");
            }
            if (c == '=')
            {
                if (peekNextChar() == '=')
                {
                    c = getNextChar();
                    return Tuple.Create(Token.TK_EQ, "==");
                }
                return Tuple.Create(Token.TK_ASSIGN, "=");
            }
            if (c == '!')
            {
                if (peekNextChar() == '=')
                {
                    c = getNextChar();
                    return Tuple.Create(Token.TK_NE, "!=");
                }
                return Tuple.Create(Token.TK_LOGNOT, "!");
            }
            if (c == '|')
            {
                if (peekNextChar() == '|')
                {
                    c = getNextChar();
                    return Tuple.Create(Token.TK_LOGOR, "||");
                }
                return Tuple.Create(Token.TK_BITOR, "|");
            }
            if (c == '&')
            {
                if (peekNextChar() == '&')
                {
                    c = getNextChar();
                    return Tuple.Create(Token.TK_LOGAND, "&&");
                }
                return Tuple.Create(Token.TK_BITAND, "&");
            }
            if (c == '>')
            {
                if (peekNextChar() == '=')
                {
                    c = getNextChar();
                    return Tuple.Create(Token.TK_GE, ">=");
                }
                return Tuple.Create(Token.TK_GT, ">");
            }
            if (c == '<')
            {
                if (peekNextChar() == '=')
                {
                    c = getNextChar();
                    return Tuple.Create(Token.TK_LE, "<=");
                }
                return Tuple.Create(Token.TK_LT, "<");
            }

            Console.Write("SynxaxError: unknown token {0}", c);
            return Tuple.Create(Token.INVALID, "invalid");
        }

        private Tuple<Token, string> make_tuple(Token i, string s)
        {
            return Tuple.Create(i, s);
        }

        public char getNextChar()
        {
            column++;
            return (char)fs.Read();
        }

        public char peekNextChar()
        { return (char)(fs.Peek()); }

        public short precedence(Token op)
        {
            switch (op)
            {
                case Token.TK_LOGOR:
                    return 1;

                case Token.TK_LOGAND:
                    return 2;

                case Token.TK_EQ:
                case Token.TK_NE:
                case Token.TK_GT:
                case Token.TK_GE:
                case Token.TK_LT:
                case Token.TK_LE:
                    return 3;

                case Token.TK_PLUS:
                case Token.TK_MINUS:
                case Token.TK_BITOR:
                    return 4;

                case Token.TK_TIMES:
                case Token.TK_MOD:
                case Token.TK_DIV:
                case Token.TK_BITAND:
                    return 5;

                default:
                    // Lowest precedence
                    return 0;
            }
        }
    }
}